/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package net.worldscale.xp.wslib.wsxml.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Vector;
import net.worldscale.xp.wslib.wsxml.sax.IAttributes;
import net.worldscale.xp.wslib.wsxml.sax.ILocator;
import net.worldscale.xp.wslib.wsxml.sax.SaxException;
import net.worldscale.xp.wslib.wsxml.sax.SaxHandler;
import net.worldscale.xp.wslib.wsxml.sax.SaxParseException;
import net.worldscale.xp.wslib.wsxml.sax.SaxParser;

/**
 *
 * @author Kun<xukun17@sina.com>
 *
 * 实现一个同时支持 XML 和 HTML 的解析器
 *
 */
final class ImplSaxParser2 extends SaxParser {

    public ImplSaxParser2() {
    }

    @Override
    public Object getProperty(String name) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void parse(InputStream is, SaxHandler h) throws IOException, SaxException {
        try {
            final MyContext pc = new MyContext();
            pc.source = is;
            pc.target = h;

            MyCore.parse(pc);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void setProperty(String name, Object value) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    private static class MyContext {

        InputStream source;
        SaxHandler target;
        InputStreamReader charReader;
        MyXmlReader xmlReader;
        MyAbstractHandlerProxy handlerProxy;
        MyLocator locator;
    }

    private static class MyCore {

        public static void parse(MyContext pc) throws SaxException, IOException {
            pc.handlerProxy.setDocumentLocator(pc.locator);
            pc.handlerProxy.startDocument();
            for (;;) {
                if (pc.xmlReader.isEOF()) {
                    break;
                }
                parseText(pc);
                if (pc.xmlReader.isEOF()) {
                    break;
                }
                parseMarkup(pc);
            }
            pc.handlerProxy.endDocument();
        }

        public static void parseText(MyContext pc) throws SaxException, IOException {
            final String str = pc.xmlReader.readUntil("<");
            char[] chs = str.toCharArray();
            pc.handlerProxy.characters(chs, 0, chs.length);
        }

        public static void parseMarkup(MyContext pc) throws SaxException, IOException {
            final MyXmlReader rdr = pc.xmlReader;
            rdr.readAndCheck("<");
            final char ch = rdr.getChar();
            if (isNameStartChar(ch)) {
                // element
                parseElementBegin(pc);
            } else {
                // non-element
                // -------------------------------------
                // <!--xxxx--> Comments
                // <![CDATA[xxx]]> CDATA Sections
                // <!DOCTYPE greeting SYSTEM "hello.dtd"> DTD
                // <?PITarget xxxx ?> Processing Instructions
                // <?xml version="1.1"?> XMLDecl
                // <tagName attrName="attrValue" /> Element
                // -------------------------------------
                if (rdr.isStartWith("/")) {
                    parseElementEnd(pc);
                } else if (rdr.isStartWith("!--")) {
                    parseComment(pc);
                } else if (rdr.isStartWith("![CDATA[")) {
                    parseCDATA(pc);
                } else if (rdr.isStartWithIgnoreCase("!DOCTYPE")) {
                    parseDTD(pc);
                } else if (rdr.isStartWith("?xml")) {
                    parseXMLDecl(pc);
                } else if (rdr.isStartWith("?")) {
                    parsePI(pc);
                } else {
                    String prefix = rdr.readChars(10);
                    throw new SaxException("Unsupported markup: <" + prefix + " ... >");
                }
            }
            rdr.readAndCheck(">");
        }

        public static void parseElementBegin(MyContext pc) throws SaxException, IOException {
            final MyXmlReader rdr = pc.xmlReader;
            final String qName = rdr.readNameString();
            rdr.skipSapce();
            final MyAttributeList attrList = new MyAttributeList();
            for (char ch = rdr.getChar(); ((ch != '>') && (ch != '/')); ch = rdr.getChar()) {
                final String key, value;
                key = rdr.readNameString();
                rdr.skipSapce();
                rdr.readAndCheck("=");
                rdr.skipSapce();
                final char mark = rdr.readChar();
                if ((mark != '\'') && (mark != '"')) {
                    throw new SaxParseException("");
                }
                value = rdr.readUntil("" + mark);
                rdr.readAndCheck("" + mark);
                attrList.addAttr(key, value);
                rdr.skipSapce();
            }
            pc.handlerProxy.startElement(null, null, qName, attrList);
            final char ch = rdr.getChar();
            if (ch == '/') {
                pc.handlerProxy.endElement(null, null, qName);
                rdr.readChar();
            }
        }

        public static void parseElementEnd(MyContext pc) throws SaxException {
            final MyXmlReader rdr = pc.xmlReader;
            rdr.readAndCheck("/");
            final String qName = rdr.readNameString();
            rdr.skipSapce();
            pc.handlerProxy.endElement(null, null, qName);
        }

        public static void parseCDATA(MyContext pc) throws SaxException {
            final MyXmlReader rdr = pc.xmlReader;
            rdr.readAndCheck("![CDATA[");
            final String str = rdr.readUntil("]]>");
            rdr.readAndCheck("]]");
            final char[] chs = str.toCharArray();
            pc.handlerProxy.characters(chs, 0, chs.length);
        }

        public static void parseComment(MyContext pc) {
            final MyXmlReader rdr = pc.xmlReader;
            rdr.readAndCheck("!--");
            pc.xmlReader.readUntil("-->");
            rdr.readAndCheck("--");
        }

        public static void parseDTD(MyContext pc) {
            final MyXmlReader rdr = pc.xmlReader;
            rdr.readUntil(">");
        }

        public static void parsePI(MyContext pc) {
            final MyXmlReader rdr = pc.xmlReader;
            rdr.readUntil(">");
        }

        public static void parseXMLDecl(MyContext pc) {
            final MyXmlReader rdr = pc.xmlReader;
            rdr.readUntil(">");
        }
    }

    private static boolean isNameChar(char ch) {
        if (isNameStartChar(ch)) {
            return true;
        } else if (('0' <= ch) && (ch <= '9')) {
            return true;
        } else {
            switch (ch) {
                case '.':
                case '-':
                    return true;
                default:
                    return false;
            }
        }
    }

    private static boolean isNameStartChar(char ch) {
        if (('a' <= ch) && (ch <= 'z')) {
            return true;
        } else if (('A' <= ch) && (ch <= 'Z')) {
            return true;
        } else {
            switch (ch) {
                case ':':
                case '_':
                    return true;
                default:
                    return false;
            }
        }
    }

    private static abstract class MyAbstractHandlerProxy extends SaxHandler {
    }

    private static class MyLocator implements ILocator {

        private int mColNum;
        private int mLineNum;
        private String mPubId;
        private String mSysId;

        public MyLocator() {
        }

        public int getColumnNumber() {
            return mColNum;
        }

        public int getLineNumber() {
            return mLineNum;
        }

        public String getPublicId() {
            return mPubId;
        }

        public String getSystemId() {
            return mSysId;
        }
    }

    private static class MyAttributeList implements IAttributes {

        private final Vector<String> mNames = new Vector<String>(2);
        private final Vector<String> mValues = new Vector<String>(2);

        public MyAttributeList() {
        }

        public void addAttr(String qName, String value) {
            if ((qName == null) || (value == null)) {
                throw new RuntimeException(
                        "The name or value of attribute is null.");
            }
            this.mNames.addElement(qName);
            this.mValues.addElement(value);
        }

        @Override
        public int getIndex(String qName) {
            return this.mNames.indexOf(qName);
        }

        @Override
        public int getIndex(String uri, String localName) {
            throw new RuntimeException("Unsupported operation.");
        }

        @Override
        public int getLength() {
            return this.mNames.size();
        }

        @Override
        public String getLocalName(int index) {
            throw new RuntimeException("Unsupported operation.");
        }

        @Override
        public String getQName(int index) {
            return this.mNames.elementAt(index);
        }

        @Override
        public String getType(int index) {
            throw new RuntimeException("Unsupported operation.");
        }

        @Override
        public String getType(String uri, String localName) {
            throw new RuntimeException("Unsupported operation.");
        }

        @Override
        public String getType(String qName) {
            throw new RuntimeException("Unsupported operation.");
        }

        @Override
        public String getURI(int index) {
            throw new RuntimeException("Unsupported operation.");
        }

        @Override
        public String getValue(String uri, String localName) {
            throw new RuntimeException("Unsupported operation.");
        }

        @Override
        public String getValue(int index) {
            return this.mValues.elementAt(index);
        }

        @Override
        public String getValue(String qName) {
            final int index = this.mNames.indexOf(qName);
            return this.mValues.elementAt(index);
        }
    }

    private static class MyXmlReaderBuffer {

        /**
         * this is a FIFO buffer.
         */
        private static class _Core {

            private final int[] data;
            private int wptr = 0;
            private int rptr = 0;

            public _Core(int size) {
                if (size < 10) {
                    size = 10;
                }
                data = new int[size];
            }

            public _Core(_Core src, int size) throws SaxParseException {
                if (size < src.data.length) {
                    size = src.data.length;
                }
                data = new int[size];
                while (src.getLength() > 0) {
                    this.write(src.read());
                }
            }

            public final void write(int ch) throws SaxParseException {
                if ((wptr - rptr) < data.length) {
                    data[(wptr++) % data.length] = ch;
                } else {
                    throw new SaxParseException("MyXmlReaderBuffer write overflow.");
                }
            }

            public final int read() throws SaxParseException {
                if (rptr < wptr) {
                    return data[(rptr++) % data.length];
                } else {
                    throw new SaxParseException("MyXmlReaderBuffer read overflow.");
                }
            }

            public int getCap() {
                return data.length;
            }

            public final int getLength() {
                return (wptr - rptr);
            }

            public boolean isFull() {
                return ((wptr - rptr) >= data.length);
            }

            public int charAt(int index) throws SaxParseException {
                if ((0 <= index) && (index < (wptr - rptr))) {
                    return data[rptr];
                } else {
                    throw new SaxParseException("MyXmlReaderBuffer get overflow.");
                }
            }
        }
        private _Core core = new _Core(16);

        public MyXmlReaderBuffer() {
        }

        public int size() {
            return core.getLength();
        }

        public int read() throws SaxParseException {
            return core.read();
        }

        public void write(int ch) throws SaxParseException {
            if (core.isFull()) {
                core = new _Core(core, core.getCap() * 2);
            }
            core.write(ch);
        }

        public int charAt(int index) throws SaxParseException {
            return core.charAt(index);
        }
    }

    private static class MyXmlReader {

        private final MyContext mpc;
        private final MyXmlReaderBuffer mBuffer;

        public MyXmlReader(MyContext pc) {
            mBuffer = new MyXmlReaderBuffer();
            mpc = pc;
        }

        public boolean isEOF() throws SaxParseException, IOException {
            final int ch;
            if (mBuffer.size() > 0) {
                ch = mBuffer.charAt(0);
            } else {
                ch = mpc.charReader.read();
                mBuffer.write(ch);
            }
            return (ch < 0);
        }

        public String readUntil(String str) {
            throw new RuntimeException("Unsupported operation.");
        }

        public void readAndCheck(String str) {
            throw new RuntimeException("Unsupported operation.");
        }

        public char getChar() {
            throw new RuntimeException("Unsupported operation.");
        }

        public String readChars(int maxLength) {
            throw new RuntimeException("Unsupported operation.");
        }

        public char readChar() {
            throw new RuntimeException("Unsupported operation.");
        }

        public String readNameString() {
            throw new RuntimeException("Unsupported operation.");
        }

        public boolean isStartWith(String str) {
            throw new RuntimeException("Unsupported operation.");
        }

        public boolean isStartWithIgnoreCase(String str) {
            throw new RuntimeException("Unsupported operation.");
        }

        public void skipSapce() {
            throw new RuntimeException("Unsupported operation.");
        }
    }
}
